package com.yukefms.engine;

import com.yukefms.engine.interfaces.FMSEngine;
import com.yukefms.engine.interfaces.PointCloudReader;
import com.yukefms.engine.running.EventCode;
import com.yukefms.engine.running.RunningContext;
import com.yukefms.engine.running.RunningEvent;
import com.yukefms.jointpoint.BodyPointCloud;
import com.yukefms.message.MessageContainer;
import com.yukefms.message.MessageProducer;
import com.yukefms.rule.interfaces.BARule;
import com.yukefms.rule.interfaces.BAStageRule;
import com.yukefms.rule.state.RuleStateFlyWeight;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description
 * @Author Ping
 * @Date 2021/6/25 17:04
 **/
@Component("mEngine")
public class MultiSenarioFMSEngine implements FMSEngine {
    public static Logger logger = LogManager.getLogger(MultiSenarioFMSEngine.class);
    /**
     * the rule to be evaluated for bodyPoint stream
     */
    private List<BAStageRule> ruleList ;
    private BAStageRule currentRule ;
    private int currIndex = 0 ;
    private int ruleSize = 0 ;
    private String beginningInfor ;
    /**
     * the context storing current bodyPoint, rule state, etc.
     */
    private RunningContext context ;
    /**
     * for caching all finished rule contexts.
     */
    private Map<String, RunningContext> ruleContextMap = new ConcurrentHashMap<>();
    /**
     * for input message
     */
    @Autowired
    private PointCloudReader reader ;
    /**
     * for output events
     */
    @Autowired
    private MessageContainer<RunningEvent> outContainer ;

    @Autowired
    MessageProducer producer ;

    private boolean allFinished = false ;
    /**
     * intializing all rule states before any actions.
     */
    static {
        RuleStateFlyWeight.doInit(null);
    }

    public MultiSenarioFMSEngine() {

    }

    /**
     * for test add some consumer and producer.
     * @param rule
     */


    @Override
    public void setBARule(BAStageRule rule) {
        List<BAStageRule> rules = new ArrayList<>() ;
        rules.add(rule) ;
        this.setBARules(rules);
    }

    @Override
    public BAStageRule getBARule() {
        return this.currentRule ;
    }

    @Override
    public PointCloudReader getReader() {
        return this.reader;
    }

    @Override
    public RunningContext currentContext() {
        return this.context;
    }

    @Override
    public void doInit() {

        this.context = new DefaultRunningContext(this.currentRule, this.producer) ;

    }

    @Override
    public void doInit(MessageProducer producer) {
        this.context = new DefaultRunningContext(this.currentRule, producer) ;
    }

    @Override
    public void startup() {
        logger.error("FMSEngine started .") ;
        reader.start() ;
        logger.error("FMSEngine start to read pointCloud") ;
        beginningInfor = getRuleNamesInfor() ;
        sendBeginInfor(beginningInfor);
        logger.error("FMSEngine sent Beginning_Info: {}", beginningInfor) ;
        RuleStateFlyWeight.setInActivatedStateForRule(currentRule) ;
        while (!allFinished) {
            // read the head message
            BodyPointCloud pointCloud = this.reader.next() ;
            if ( pointCloud == null) { continue; }
            if (this.reader.ended()) {
                logger.error("the reader was stopped since not reading for max_waiting_time.") ;
                context.notifyRunningEventListener(EventCode.INPUT_ERROR,currentRule.ruleName());
                break ;
            }
            // for normal pointCloud, do reaction.
            currentRule.doReaction(pointCloud, context);

            // update context
            // check if the result was gotten by query context
            if (context.ruleIsFinished() ) {
                // reset context for next rule
                if (this.hasNext()) {
                    this.toNext();
                    this.resetContext(this.currentRule);
                } else {
                    // all finished.
                    logger.error("All rules are finished ") ;
                    context.notifyRunningEventListener(EventCode.ALL_FINISHED, "ALL_ACTION");
                    allFinished = true ;
                }
            }
        }
        shutdown() ;
    }

    @Override
    public void shutdown() {
        logger.error("Now, the engine was stop.");
        this.reader.stop();
    }


    @Override
    public void setBARules(List<BAStageRule> ruleList) {
        this.ruleList = ruleList ;
        this.currentRule = this.ruleList.get(0) ;
        this.currIndex = 0 ;
        this.ruleSize = this.ruleList.size() ;
    }

    @Override
    public BAStageRule currentRule() {
        return this.currentRule;
    }

    @Override
    public boolean currentIsFinished() {
        return context.ruleIsFinished();
    }

    @Override
    public boolean allFinished() {
        return currentIsFinished() && ! hasNext();
    }

    @Override
    public boolean hasNext() {
        return (this.currIndex + 1) < this.ruleSize;
    }

    @Override
    public void toNext() {
        this.currentRule = this.ruleList.get(++this.currIndex) ;
        logger.error("NEXT RULE :{}", this.currentRule.ruleName()) ;
    }

    @Override
    public void resetContext(BAStageRule newRule) {
        // caching finished rule context
        this.ruleContextMap.put(currentRule.ruleName(), this.context) ;
        this.context = new DefaultRunningContext(newRule, this.producer) ;
        //RuleStateFlyWeight.doInit(currentRule);
        RuleStateFlyWeight.setInActivatedStateForRule(currentRule) ;

    }

    @Override
    public RunningContext getContextByRuleName(String ruleName) {
        return this.ruleContextMap.get(ruleName);
    }

    @Override
    public Map<String, RunningContext> getRuleContextMap() {
        return this.ruleContextMap;
    }

    @Override
    public MessageProducer getProducer() {
        return this.producer;
    }

    /**
     * coding beginning rule information to the Front. The beginning information is of following format:
     * BEGIN_CODE#CURRENT_RULE#RULE1#RULE2#...
     * @return
     */
    private String getRuleNamesInfor() {
        StringBuffer sb = new StringBuffer(BEGIN_CODE) ;
        sb.append("#") ;
        sb.append(currentRule.ruleName()) ;
        sb.append("#") ;
        for (int i=0; i < ruleList.size(); i++) {
            sb.append(ruleList.get(i).ruleName()) ;
            if (i != ruleList.size() -1 ) {
                sb.append("#") ;
            }
        }
        return sb.toString() ;
    }
}
